package cn.bugstack.springframework.beans.factory.support;

import cn.bugstack.springframework.beans.BeansException;
import cn.bugstack.springframework.beans.factory.FactoryBean;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * FactoryBeanRegistrySupport类主要处理关于FactoryBean类对象的注册操作，之所以放到一个单独的类中，
 * 是为了不同领域模块下的类只负责各自需要完成的功能，避免因扩展而导致类膨胀，难以维护。
 * doGetObjectFromFactoryBean是获取FactoryBean#getObject的方法，因为既要处理缓存又要获取对象，
 * 所以额外提供了doGetObjectFromFactoryBean方法进行逻辑包装。
 */
public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {

    /**
     * Cache of singleton objects created by FactoryBeans: FactoryBean name --> object
     * 这段代码创建了一个线程安全的 ConcurrentHashMap 实例，用于缓存 FactoryBean 的实例化对象。
     * 在Spring容器中，当一个bean的定义是一个 FactoryBean 实现类时，Spring会先从该缓存中尝试获取实例化对象，
     * 如果缓存中存在，则直接返回缓存中的对象，否则会调用 FactoryBean 的 getObject() 方法创建一个新的实例，并将其放入缓存中。
     * 这样做的目的是为了优化性能，在多次获取同一个 FactoryBean 对象时，避免重复创建实例，提高效率。
     * 同时，由于 ConcurrentHashMap 是线程安全的，所以在多线程环境下也能保证缓存的安全性。
     */
    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();


    /**
     * 从工厂bean缓存中获取对象。如果对象存在且不为特定的NULL_OBJECT标志，则返回该对象；否则返回null。
     * @param beanName 要获取的对象在缓存中的名称
     * @return 缓存中对应名称的对象，如果不存在或为NULL_OBJECT，则返回null
     */
    protected Object getCachedObjectForFactoryBean(String beanName) {
        Object object = this.factoryBeanObjectCache.get(beanName);
        return (object != NULL_OBJECT ? object : null);
    }

    /**
     * 从工厂bean中获取对象。如果工厂bean是单例模式，会尝试从缓存中获取对象，未缓存时创建并缓存；
     * 非单例模式则直接创建对象。
     * @param factory 工厂bean实例
     * @param beanName 要获取的对象名称
     * @return 从工厂bean中获取到的对象
     */
    protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {
        if (factory.isSingleton()) {
            // 处理单例模式的工厂bean，先尝试从缓存获取对象
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                // 缓存未命中，从工厂bean直接获取对象并缓存
                object = doGetObjectFromFactoryBean(factory, beanName);
                this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
            }
            return (object != NULL_OBJECT ? object : null);
        } else {
            // 非单例模式直接从工厂bean获取对象
            return doGetObjectFromFactoryBean(factory, beanName);
        }
    }

    /**
     * 从指定的工厂bean获取对象。如果获取过程中发生异常，将抛出BeansException。
     * @param factory 工厂bean实例
     * @param beanName 要获取的对象名称
     * @return 从工厂bean获取到的对象
     * @throws BeansException 如果工厂bean在创建对象时抛出异常，则抛出BeansException
     */
    private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
        try {
            // ProxyBeanFactory是一个实现FactoryBean接口的代理类的名称，主要模拟了UerDao的原有功能，类似于MyBatis框架中的代理操作。
            // getObject方法提供了一个InvocationHandler代理对象，当调用getObject方法时，执行InvocationHandler代理对象的功能。
            return factory.getObject();
        } catch (Exception e) {
            // 将工厂bean在创建对象时抛出的异常转换为BeansException抛出
            throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
        }
    }

}
